
package com.scs.application.modules.settlement.service.impl;

import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.collect.Lists;
import com.scs.application.consts.SerialRuleConsts;
import com.scs.application.core.exceptions.BusinessException;
import com.scs.application.core.serial.SerialUtils;
import com.scs.application.core.service.BaseServiceImpl;
import com.scs.application.core.utils.BeanUtils;
import com.scs.application.core.utils.SerialOwnerUtils;
import com.scs.application.core.utils.StringUtils;
import com.scs.application.core.utils.UserUtils;
import com.scs.application.modules.base.entity.Hospital;
import com.scs.application.modules.base.mapper.HospitalMapper;
import com.scs.application.modules.settlement.consts.DictConsts;
import com.scs.application.modules.settlement.consts.InvoiceGroupType;
import com.scs.application.modules.settlement.dto.InvoiceBuildDTO;
import com.scs.application.modules.settlement.dto.InvoiceDTO;
import com.scs.application.modules.settlement.dto.InvoiceItemDTO;
import com.scs.application.modules.settlement.entity.Bill;
import com.scs.application.modules.settlement.entity.BillItem;
import com.scs.application.modules.settlement.entity.Invoice;
import com.scs.application.modules.settlement.mapper.BillItemMapper;
import com.scs.application.modules.settlement.mapper.InvoiceLineMapper;
import com.scs.application.modules.settlement.mapper.InvoiceMapper;
import com.scs.application.modules.settlement.request.BillItemQueryRequest;
import com.scs.application.modules.settlement.request.InvoiceQueryRequest;
import com.scs.application.modules.settlement.service.BillItemService;
import com.scs.application.modules.settlement.service.BillService;
import com.scs.application.modules.settlement.service.InvoiceItemService;
import com.scs.application.modules.settlement.service.InvoiceService;
import com.scs.application.modules.settlement.vo.BillItemVO;
import com.scs.application.modules.settlement.vo.InvoiceVO;
import com.scs.application.modules.thirdparty.request.InvoiceRequest;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 开票单 service 实现
 */
@Slf4j
@AllArgsConstructor
@Service
@Transactional(rollbackFor = Exception.class)
public class InvoiceServiceImpl extends BaseServiceImpl<InvoiceMapper, Invoice> implements InvoiceService {

    private final InvoiceLineMapper invoiceLineMapper;

    private final InvoiceItemService invoiceItemService;

    private final BillItemService billItemService;

    private final BillService billService;

    private final BillItemMapper billItemMapper;

    private final HospitalMapper hospitalMapper;

    /*@Override
    public boolean removeCascadeById(Serializable id) {
        invoiceLineMapper.delete(Wrappers.<InvoiceLine>query().eq("invoice_id", id));
        invoiceItemService.remove(Wrappers.<InvoiceItem>query().eq("invoice_id", id));
        return super.removeById(id);
    }*/

    @Override
    public List<Invoice> list(InvoiceQueryRequest request) {
        List<Invoice> list = this.list(Wrappers.<Invoice>query()
                .like(StringUtils.isNotBlank(request.getHospitalName()), "hospital_name", request.getHospitalName())
                .between(request.getBeginGmtMakeOut() != null && request.getEndGmtMakeOut() != null, "gmt_make_out", request.getBeginGmtMakeOut(), request.getEndGmtMakeOut())
                .orderByDesc("gmt_modified")
        );
        return list;
    }

    @Override
    public IPage<Invoice> page(InvoiceQueryRequest request) {
        return this.page(request.getPage(), getQueryWrapper(request));
    }

    private QueryWrapper<Invoice> getQueryWrapper(InvoiceQueryRequest request) {
        Date beginGmtMakeOut = request.getBeginGmtMakeOut();

        QueryWrapper<Invoice> wrapper = Wrappers.<Invoice>query()
                .eq(request.getStatus() != null, "status", request.getStatus())
                .like(StringUtils.isNotBlank(request.getCode()), "code", request.getCode())
                .like(StringUtils.isNotBlank(request.getHospitalName()), "hospital_name", request.getHospitalName())
                .between(beginGmtMakeOut != null && request.getEndGmtMakeOut() != null, "gmt_make_out", beginGmtMakeOut, request.getEndGmtMakeOut())
                .orderByDesc("code");
        if (UserUtils.isHospital()) {
            wrapper.eq("hospital_id", UserUtils.getHospitalId());
        }
        if (UserUtils.isSupplier()) {
            wrapper.eq("supplier_id", UserUtils.getSupplierId());
        }
        return wrapper;
    }

    @Override
    public boolean add(Invoice entity) {
        entity.setCode(SerialUtils.generateCode(SerialRuleConsts.SETTLEMENT_INVOICE_CODE, SerialOwnerUtils.getSupplierOwner(UserUtils.getSupplierId())))
                .setDrawer(UserUtils.getSupplier().getName())
                .setSupplierId(UserUtils.getSupplierId())
                .setStatus(DictConsts.INVOICE_STATUS_UNSUBMIT)
                .setSupplierName(UserUtils.getSupplier().getName());
        return saveCascadeById(entity);

    }

    /**
     * 撤销开票
     *
     * @param entity
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancel(Invoice entity) {
        Invoice invoice = this.getById(entity.getId()); //再获取一次
        if (invoice == null) {
            return;
        }
        if (invoice.getSyncStatus() != null && invoice.getSyncStatus() == DictConsts.INVOICE_SYNC_STATUS_YES) { //已经同步到SPD的不允许撤销
            throw new BusinessException("该发票已经同步到SPD系统，不允许撤销，请先致SPD将发票撤销！");
        }

        //还原BillItems
        List<BillItem> billItems = billItemService.list(Wrappers.<BillItem>query().eq("invoice_id", entity.getId()));
        billItems.forEach(billItem -> billItem.setStatus(DictConsts.BILL_ITEM_STATUS_UNINVOICE));
        billItemService.updateBatchById(billItems);

        //还原Bills
        List<Bill> bills = billService.list(Wrappers.<Bill>query().in("id", billItems.stream().map(BillItem::getBillId).distinct().collect(Collectors.toList())));
        bills.forEach(bill -> bill.setStatus(DictConsts.BILL_STATUS_SETTLEABLE));
        billService.updateBatchById(bills);

        invoice.setStatus(DictConsts.INVOICE_STATUS_UNSUBMIT);
        this.updateById(invoice);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void audit(Invoice invoice) {
        Invoice invoiceDb = getById(invoice.getId());
        //是否为空
        if (invoiceDb == null) {
            throw new BusinessException("开票单已被删除，请刷新重试");
        }

        List<BillItem> list = billItemService.list(Wrappers.<BillItem>query().eq("invoice_id", invoice.getId()));
        if (list.isEmpty()) {
            throw new BusinessException("开票明细不能为空");
        }
        //当前状态是否是制单状态
        if (invoiceDb.getStatus() != DictConsts.INVOICE_STATUS_UNSUBMIT) {
            throw new BusinessException("当前状态不是制单状态，无法提交");
        }
        //通过开票单ID、记账单状态可开票、记账明细状态未开票，查询所有记账单明细，
        List<BillItemVO> billItemVOS = billItemService.listBillItems(new BillItemQueryRequest().setInvoiceId(invoice.getId())
                .setSupplierId(UserUtils.getSupplierId()));
//        List<BillItem> billItems = Lists.newArrayListWithCapacity(billItemVOS.size());
        List<Bill> bills = Lists.newArrayList();
        //修改记账单明细状态
//        billItemVOS.stream().forEach(m -> {
//            BillItem billItem = new BillItem();
//            billItem.setStatus(DictConsts.BILL_ITEM_STATUS_INVOICED)
//                    .setId(m.getId());
//            billItems.add(billItem);
//        });
//        billItemService.updateBatchById(billItems);
        //修改记账单状态
        List<String> billIds = billItemVOS.stream().map(n -> n.getBillId()).distinct().collect(Collectors.toList());
        if (billIds.size() != 0) {
            for (int i = 0; i < billIds.size(); i++) {
                //查询记账单对应明细是否都已记账
                int count = billItemService.count(Wrappers.<BillItem>query().eq("bill_id", billIds.get(i))
                        .eq("status", DictConsts.BILL_ITEM_STATUS_UNINVOICE));
                if (count == 0) {
                    Bill bill = new Bill();
                    bill.setStatus(DictConsts.BILL_STATUS_INVOICED).setId(billIds.get(i));
                    bills.add(bill);
                }
            }

        }
        if (!bills.isEmpty()) {
            billService.updateBatchById(bills);
        }
        //开票单状态修改为已审核
        invoice.setStatus(DictConsts.INVOICE_STATUS_AUDITED)
                .setAuditorName(UserUtils.currentUser().getNickname())
                .setGmtAudited(new Date());
        //提交
        updateById(invoice);
    }

    @Override
    public Double saveInvoice(InvoiceDTO invoiceDTO) {
        //开票单信息
        Invoice invoice = new Invoice();
        BeanUtils.copyProperties(invoiceDTO, invoice, Invoice.class);
        if (StringUtils.isBlank(invoice.getId())) {
            invoice.setCode(SerialUtils.generateCode(SerialRuleConsts.SETTLEMENT_INVOICE_CODE, SerialOwnerUtils.getSupplierOwner(UserUtils.getSupplierId())))
                    .setSupplierId(UserUtils.getSupplierId())
                    .setSupplierName(UserUtils.getSupplier().getName())
                    .setGmtMakeOut(new Date())
                    .setDrawer(UserUtils.getSupplier().getName())
                    .setStatus(DictConsts.INVOICE_STATUS_UNSUBMIT)
                    .setGroupByCondition(invoiceDTO.getGroupByCondition());
            this.save(invoice);
        } else {
            invoice.setTotalAmount(invoiceDTO.getTotalAmount()).setId(invoice.getId());
            this.updateById(invoice);
        }
        //开票单明细
        List<InvoiceItemDTO> dtoItems = invoiceDTO.getItems();
        List<BillItem> billItems = Lists.newArrayList();
        dtoItems.stream().forEach(o -> {
            List<BillItemVO> billItemVOS = Lists.newArrayList();
            //通过期号、医院Id、耗材编码、记账单状态(可开票)、记账单明细状态(未开票)查询所有记账单明细，
            /*if( "0".equals(invoiceDTO.getGroupByLot())){
                //如果不根据批次
                billItemVOS = billItemService.listBillItems(new BillItemQueryRequest().setHospitalId(invoice.getHospitalId())
                        .setPeriodNo(invoice.getPeriodNo())
                        .setMaterialCode(o.getMaterialCode())
                        .setInvoiceId("0")
                        .setBStatus(DictConsts.BILL_STATUS_SETTLEABLE)
                        .setStatus(DictConsts.BILL_ITEM_STATUS_UNINVOICE)
                );
            }else{
                //通过期号、医院Id、耗材编码、批次、记账单状态(可开票)、记账单明细状态(未开票)查询所有记账单明细，
                billItemVOS = billItemService.listBillItems(new BillItemQueryRequest().setHospitalId(invoice.getHospitalId())
                        .setPeriodNo(invoice.getPeriodNo())
                        .setMaterialCode(o.getMaterialCode())
                        .setLot(o.getLot())
                        .setInvoiceId("0")
                        .setBStatus(DictConsts.BILL_STATUS_SETTLEABLE)
                        .setStatus(DictConsts.BILL_ITEM_STATUS_UNINVOICE)
                );
            }*/

            billItemVOS = billItemService.listBillItems(new BillItemQueryRequest().setHospitalId(invoice.getHospitalId())
                    .setSupplierId(UserUtils.getSupplierId())
                    .setPeriodNo(invoice.getPeriodNo())
                    .setMaterialCode(o.getMaterialCode())
                    .setMaterialName(o.getMaterialName())
                    .setLot(o.getLot())
                    .setInvoiceId("0")
                    .setBStatus(DictConsts.BILL_STATUS_SETTLEABLE)
                    .setStatus(DictConsts.BILL_ITEM_STATUS_UNINVOICE)
                    .setDeptName(o.getDeptName())
                    .setPrice(o.getPrice())
            );
            if (billItemVOS == null) {
                throw new BusinessException("未查询到开票单对应记账明细数据");
            }
            //记账单明细添加开票id
            billItemVOS.stream().forEach(m -> {
                BillItem billItem = new BillItem();
                billItem.setInvoiceId(invoice.getId())
                        .setId(m.getId());
                billItems.add(billItem);
            });
        });

        billItemService.updateBatchById(billItems);

        //invoice.setTotalAmount(invoiceDTO.getTotalAmount()).setId(invoice.getId());
        this.calculatePrice(invoice.getId());

        //重新获取价格
        return this.getById(invoice.getId()).getTotalAmount();
    }

    @Override
    public boolean deleteInvoiceById(String id) {
        List<BillItem> billItemList = billItemService.list(Wrappers.<BillItem>query().eq("invoice_id", id));
        List<BillItem> itemList = Lists.newArrayListWithCapacity(billItemList.size());
        if (billItemList.size() > 0) {
            for (BillItem item : billItemList) {
                BillItem billItem = new BillItem();
                billItem.setInvoiceId("0").setId(item.getId());
                itemList.add(billItem);
            }
            billItemService.updateBatchById(itemList);
        }
        boolean b = this.removeById(id);
        return b;
    }

    /**
     * 获取发票信息
     *
     * @param invoiceRequest
     * @return
     */
    @Override
    public List<InvoiceVO> getInvoices(InvoiceRequest invoiceRequest) {
        List<Invoice> invoices = super.list(Wrappers.<Invoice>query().eq("hospital_id", UserUtils.getHospitalId())
                .eq("status", DictConsts.INVOICE_STATUS_AUDITED) //0：制单，1：已审核
                .eq(StringUtils.isNotBlank(invoiceRequest.getSupplierId()), "supplier_id", invoiceRequest.getSupplierId())
                .gt(invoiceRequest.getBeginDate() != null, "gmt_modified", invoiceRequest.getBeginDate()));

        if (invoices.size() == 0) {
            return null;
        }

        List<InvoiceVO> invoiceVOS = Lists.newArrayListWithCapacity(invoices.size());

        List<BillItem> billItems = billItemService.list(Wrappers.<BillItem>query().in("invoice_id", invoices.stream().map(invoice -> invoice.getId())
                .collect(Collectors.toList())));

        List<Bill> bills = billService.list(Wrappers.<Bill>query().in("id", billItems.stream().map(billItem -> billItem.getBillId())
                .collect(Collectors.toList())));

        invoices.forEach(invoice -> {
            InvoiceVO invoiceVO = new InvoiceVO();
            BeanUtils.copyProperties(invoice, invoiceVO);
            invoiceVO.setItems(billItems.stream().filter(billItem -> billItem.getInvoiceId().equals(invoice.getId()))
                    .map(billItem -> {
                        BillItemVO billItemVO = new BillItemVO();
                        BeanUtils.copyProperties(billItem, billItemVO);
                        Bill bill = bills.stream().filter(bl -> bl.getId().equals(billItemVO.getBillId())).findAny().orElse(null);
                        if (bill != null) {
                            billItemVO.setDeptName(bill.getDeptName());
                            billItemVO.setDeptCode(bill.getDeptCode());
                        }
                        return billItemVO;
                    }).collect(Collectors.toList()));
            invoiceVOS.add(invoiceVO);
        });

        return invoiceVOS;
    }

    @Override
    public void calculatePrice(String invoiceId) {
        baseMapper.calculatePrice(invoiceId);
    }

    /**
     * 恢复结算单到待审核状态
     */
    @Override
    public boolean resumeInvoice(String id) {
        Invoice invoice = this.getById(id);
        if (invoice == null) {
            throw new BusinessException("你输入的记账单ID不正确");
        }

        if (invoice.getStatus().equals(DictConsts.INVOICE_STATUS_UNSUBMIT)) {
            throw new BusinessException("已经是待审核状态，无需修正");
        }

        invoice.setStatus(DictConsts.INVOICE_STATUS_UNSUBMIT);
        this.updateById(invoice);

        List<BillItem> billItemList = billItemService.list(Wrappers.<BillItem>query().eq("invoice_id", id));
        billItemList.forEach(billItem -> billItem.setStatus(DictConsts.BILL_ITEM_STATUS_UNINVOICE));
        billItemService.updateBatchById(billItemList);

        List<Bill> bills = billService.list(Wrappers.<Bill>query().in("id", billItemList.stream().map(BillItem::getBillId).distinct().collect(Collectors.toList())));
        bills.forEach(bill -> bill.setStatus(DictConsts.BILL_ITEM_STATUS_INVOICED));
        billService.updateBatchById(bills);
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removeCascadeBatchByIds(Collection<? extends Serializable> idList) {
        idList.forEach(id -> {
            Invoice invoice = this.getById(id);
            BusinessException.throwErrorIf(invoice == null, "开票单已被删除，请刷新重试");
            BusinessException.throwErrorIf(DictConsts.INVOICE_STATUS_UNSUBMIT != invoice.getStatus(), "开票单非制单状态，不可操作");
        });

        //还原BillItems
        List<BillItem> billItems = billItemService.list(Wrappers.<BillItem>query().in("invoice_id", idList));
        if (!billItems.isEmpty()) {
            billItems.forEach(billItem -> billItem.setStatus(DictConsts.BILL_ITEM_STATUS_UNINVOICE).setInvoiceId("0"));
            billItemService.updateBatchById(billItems);
        }

        // 更新开票金额
        idList.forEach(id -> {
            this.calculatePrice((String) id);
        });

        return super.removeCascadeBatchByIds(idList);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void deleteBatchItem(List<Serializable> ids) {
        List<BillItem> billItems = billItemService.list(Wrappers.<BillItem>query().in("id", ids));
        BusinessException.throwErrorIf(ObjectUtil.length(billItems) != ObjectUtil.length(ids), "开票明细发生变化，请刷新重试");
        Invoice invoice = this.getById(billItems.get(0).getInvoiceId());
        BusinessException.throwErrorIf(invoice == null, "开票单已被删除，请刷新重试");
        BusinessException.throwErrorIf(DictConsts.INVOICE_STATUS_UNSUBMIT != invoice.getStatus(), "开票单非制单状态，不可操作");

        //还原BillItems
        billItems.forEach(billItem -> billItem.setStatus(DictConsts.BILL_ITEM_STATUS_UNINVOICE).setInvoiceId("0"));
        billItemService.updateBatchById(billItems);
        invoice.setStatus(DictConsts.INVOICE_STATUS_UNSUBMIT);
        this.updateById(invoice);

        // 更新开票金额
        this.calculatePrice(invoice.getId());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<Invoice> buildInvoice(InvoiceBuildDTO invoiceDTO) {
        Hospital hospital =  hospitalMapper.selectById(invoiceDTO.getHospitalId());
        BusinessException.throwErrorIf(hospital == null, "医院不存在，请联系运营");

        //开票单信息
        Invoice invoice;
        List<Invoice> invoiceList = new ArrayList<>();

        //开票单汇总明细
        List<BillItemVO> dtoItems = invoiceDTO.getItems();
        List<BillItem> billItems = Lists.newArrayList();
        List<BillItemVO> billItemVOS;
        Double totalAmount = 0d;
        QueryWrapper<BillItemVO> queryWrapper;

        List<String> billItemIdList = Lists.newArrayList();
        Double groupAmount,groupQty;
        boolean hasPeriod = false;
        for(BillItemVO groupItem : dtoItems) {
            queryWrapper = Wrappers.<BillItemVO>query();
            queryWrapper.eq("hospital_id", invoiceDTO.getHospitalId());
            queryWrapper.eq("supplier_id", UserUtils.getSupplierId());
            queryWrapper.eq("material_name", groupItem.getMaterialName());
            queryWrapper.eq("bi.status", DictConsts.BILL_ITEM_STATUS_UNINVOICE);
            queryWrapper.eq("material_spec", groupItem.getMaterialSpec());
            queryWrapper.eq("ifnull(invoice_id,'0')", "0");
            hasPeriod = false;
            switch (invoiceDTO.getGroupType()) {
                //期号+品名+品规+批次+价格
                case InvoiceGroupType.period1: {
                    queryWrapper.eq("period_no",groupItem.getPeriodNo());
                    queryWrapper.eq("lot",groupItem.getLot());
                    queryWrapper.eq("price",groupItem.getPrice());
                    hasPeriod = true;
                    break;
                }
                //期号+品名+品规+价格
                case InvoiceGroupType.period2: {
                    queryWrapper.eq("period_no",groupItem.getPeriodNo());
                    queryWrapper.eq("price",groupItem.getPrice());
                    hasPeriod = true;
                    break;
                }

                //记账单号+品名+品规+批次+价格
                case InvoiceGroupType.bill1: {
                    queryWrapper.eq("bill_id",groupItem.getBillId());
                    queryWrapper.eq("lot",groupItem.getLot());
                    queryWrapper.eq("price",groupItem.getPrice());
                    break;
                }
            }

            billItemVOS = billItemMapper.listDtlForInvoiceAdd(queryWrapper);
            BusinessException.throwErrorIf(billItemVOS.isEmpty(), "未找到相关记账明细，请刷新重试");

            groupAmount = billItemVOS.stream().mapToDouble(BillItemVO::getSubtotalAmount).sum();
            groupQty = billItemVOS.stream().mapToDouble(BillItemVO::getQty).sum();

            BusinessException.throwErrorIf(!NumberUtil.equals(groupAmount,groupItem.getSubtotalAmount()), "金额发生变化，请刷新重试");
            BusinessException.throwErrorIf(!NumberUtil.equals(groupQty,groupItem.getQty()), "数量发生变化，请刷新重试");

            // 开票单
            invoice = new Invoice();
            invoice.setSupplierId(UserUtils.getSupplierId())
                    .setSupplierName(UserUtils.getSupplier().getName())
                    .setGmtMakeOut(new Date())
                    .setDrawer(UserUtils.getSupplier().getName())
                    .setStatus(DictConsts.INVOICE_STATUS_UNSUBMIT)
                    .setGroupByCondition(invoiceDTO.getGroupType());
            invoice.setHospitalId(hospital.getId()).setHospitalName(hospital.getName());
            invoice.setTotalAmount(groupAmount);
            invoice.setPeriodNo(groupItem.getPeriodNo());
            invoice.setCode(SerialUtils.generateCode(SerialRuleConsts.SETTLEMENT_INVOICE_CODE, SerialOwnerUtils.getSupplierOwner(UserUtils.getSupplierId())));
            this.save(invoice);

            // 开票明细
            billItemIdList =billItemVOS.stream().map(BillItemVO::getId).collect(Collectors.toList());
            billItemService.update(Wrappers.<BillItem>update().in("id",billItemIdList).set("invoice_id", invoice.getId()).set("status", DictConsts.BILL_ITEM_STATUS_INVOICED));
            invoiceList.add(invoice);
        }

        return invoiceList;
    }
}
